using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

namespace ebcpatterns.rx
{
    public class ToIEnumerable<T>
    {
        private readonly ConcurrentQueue<T> _values = new ConcurrentQueue<T>();
        private readonly AutoResetEvent _newValuesArrived = new AutoResetEvent(false);
        private readonly ManualResetEvent _eventHandlerHasBeenAssigned = new ManualResetEvent(false);

        private readonly T _endOfStreamValue;


        public ToIEnumerable() : this(default(T)) { }
        public ToIEnumerable(T endOfStreamValue)
        {
            _endOfStreamValue = endOfStreamValue;
            ThreadPool.QueueUserWorkItem(_ =>
                                             {
                                                 _eventHandlerHasBeenAssigned.WaitOne();
                                                 while (true)
                                                 {
                                                     var values = EnumerateValues();
                                                     _allValues(values);
                                                 }
                                             }, null);
        }


        private IEnumerable<T> EnumerateValues()
        {
            while (true)
            {
                var endOfList = false;

                T value;
                while (_values.TryDequeue(out value))
                {
                    if (object.Equals(value, _endOfStreamValue))
                    {
                        endOfList = true;
                        break;
                    }
                    yield return value;
                }

                if (endOfList) break;
                _newValuesArrived.WaitOne();
            }
        }


        public void Process(T value)
        {
            _values.Enqueue(value);
            _newValuesArrived.Set();
        }


        private event Action<IEnumerable<T>> _allValues;
        public event Action<IEnumerable<T>> AllValues
        {
            add
            {
                _allValues += value;
                _eventHandlerHasBeenAssigned.Set();
            }

            remove { _allValues -= value; }
        }
    }
}